use crate::engine::{
    njm_buffer::{NjmBuffer, DEFAULT_MAP_OFFSET, DEFAULT_MAP_SIZE, DEFAULT_MIN_OFFSET_ALIGN}, njm_camera::NjmCamera, njm_descriptor::{NjmDescriptorPool, NjmDescriptorPoolBuilder, NjmDescriptorSetLayout, NjmDescriptorSetLayoutBuilder, NjmDescriptorWriter}, njm_device::NjmDevice, njm_frame_info::*, njm_game_obj::{self, NjmGameObj}, njm_img::{self, NjmImg}, njm_keyboard_ctrl::*, njm_model::{self, NjmModel}, njm_renderer::NjmRenderer, njm_swapchain::{NjmSwapChain, MAX_FRAMES_IN_FLIGHT}, njm_tex::{self, NjmTex, TexImgBingding}, njm_utils::*, oop_render_system::simple_render_sys::SimpleRenderSystem
};
use ash::{
    Device, Entry, Instance,
    ext::{debug_utils, device_fault},
    vk::{self, Extent2D, Handle},
};
use cgmath::{Deg, Euler, Matrix3, Matrix4, Quaternion, Rad, Vector3, Vector4, prelude::*};
use core::f32;
use std::{
    cmp::min, collections::HashMap, ffi::{CStr, CString}, ops::Deref, os::raw::*, sync::{Arc, Mutex, MutexGuard}, time::*
};
use winit::{
    application::ApplicationHandler,
    dpi::PhysicalSize,
    event::{self, ElementState, Event, WindowEvent},
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop, EventLoopBuilder},
    keyboard::{KeyCode, *},
    window::{Window, WindowAttributes, WindowId},
};

const DEFAULT_WINDOW_NAME: &str = "my_window";
const DEFAULT_WINDOW_TITLE: &str = "my window title";
const DEFAULT_WINDOW_WIDTH: u32 = 800;
const DEFAULT_WINDOW_HEIGHT: u32 = 600;

const MAX_FRAME_TIME: f32 = 0.05;
const DEFAULT_CAMERA_TRANSLATION_Z: f32 = -2.5;
const DEFAULT_CAMERA_FOVY: f32 = f32::consts::FRAC_PI_4;
const DEFAULT_CAMERA_NEAR_PLANE: f32 = 0.1;
const DEFAULT_CAMERA_FAR_PLANE: f32 = 100.0;

const SDR_BASE: &str = "src/shaders/";
const VERT_PATH: &str = "simple_shader_vert.spv";
const FRAG_PATH: &str = "simple_shader_frag.spv";

const IMG_BASE: &str = "src/texs/";
const MODEL_BASE: &str = "src/models/";

#[derive(Default)]
pub struct AppOop<'a> {
    wnd_attrs: Option<WindowAttributes>,
    wnd: Option<Window>,

    dev: Option<Arc<Mutex<NjmDevice>>>,
    renderer: Option<NjmRenderer>,

    current_time: Option<Instant>,
    move_ctrl: Option<KeyboardMovementController>,
    camera: Option<NjmCamera>,
    viewer_obj: Option<NjmGameObj>,

    ubo_bufs: Option<Vec<Box<NjmBuffer>>>,
    imgs: Option<njm_img::Map>,
    texs: Option<Vec<Box<NjmTex>>>,
    models: Option<njm_model::Map>,
    objs: Option<njm_game_obj::Map>,

    global_desc_pool: Option<Box<NjmDescriptorPool>>,
    global_layout_builder: Option<NjmDescriptorSetLayoutBuilder<'a>>,
    global_desc_sets: Option<Vec<vk::DescriptorSet>>,
    render_sys: Option<SimpleRenderSystem>,
}

impl AppOop<'_> {
    pub fn get_wnd_attrs(&self) -> Option<WindowAttributes> {
        return self.wnd_attrs.clone();
    }
    pub fn set_wnd_attrs(&mut self, base: WindowAttributes) {
        let tmp: WindowAttributes = base
            .with_inner_size(PhysicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT))
            .with_title(DEFAULT_WINDOW_TITLE);
        self.wnd_attrs = Some(tmp);
    }

    pub fn run(&mut self) {
        println!("----- app-oop::run begin ------");

        println!("----- app-oop::run end ------");
    }

    pub fn redraw(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId) {
        if self.renderer.is_none() {
            println!("app::redraw : self.renderer is None!");
            return;
        }
        println!("app::redraw : renderer exist, begin redraw");
        let renderer = self.renderer.as_mut().unwrap();
        let aspect = renderer.get_aspect_ratio();

        // time calc
        let new_time = Instant::now();
        let mut frame_time: Duration = if let Some(instant) = self.current_time {
            instant.elapsed()
        } else {
            Duration::ZERO
        };
        self.current_time = Some(new_time);
        frame_time = min(frame_time, Duration::from_secs_f32(MAX_FRAME_TIME));

        // update camera
        let move_ctrl = self.move_ctrl.as_ref().unwrap();
        let viewer_obj = self.viewer_obj.as_mut().unwrap();
        let cam = self.camera.as_mut().unwrap();
        move_ctrl.move_in_plane_xz(frame_time.as_secs_f32(), viewer_obj);
        cam.set_view_yxz(viewer_obj.transform.translation, viewer_obj.transform.rotation);
        cam.set_perspective_projection(DEFAULT_CAMERA_FOVY, aspect, DEFAULT_CAMERA_NEAR_PLANE, DEFAULT_CAMERA_FAR_PLANE);
        println!("camera pos: {:?}", cam.get_position());

        // rendering
        if let cmdbuf = renderer.begin_frame()
            && !cmdbuf.is_null()
        {
            println!("renderer::begin_frame : succeed");
            // data op
            let frame_idx = renderer.get_current_frame_index();
            let frame_info = FrameInfo {
                frame_index: frame_idx,
                frame_time: frame_time,
                cmd_buf: cmdbuf,
                camera: cam,
                desc_set: self.global_desc_sets.as_ref().unwrap()[frame_idx],
                objs: self.objs.as_ref().unwrap(),
            };
            let ubo = GlobalUbo {
                view: *cam.view(),
                projection: *cam.proj(),
                inverse_view: *cam.view_inverse(),
                ambient_light_color: [0.8, 0.8, 0.8, 1.0].into(),
            };
            let current_ubobuf = &self.ubo_bufs.as_ref().unwrap()[frame_idx];
            current_ubobuf.write_to_buffer(&ubo as *const GlobalUbo as *const c_void, DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);
            current_ubobuf.flush(DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);

            // render op
            renderer.begin_render_pass(cmdbuf);
            self.render_sys.as_ref().unwrap().render(&frame_info);

            // end render
            renderer.end_render_pass(cmdbuf);
            renderer.end_frame(); // fatal problem!
            println!("renderer::end_frame : succeed");
        }
        self.wnd.as_ref().unwrap().request_redraw();
    }

    pub fn handle_keyboard_input(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        device_id: winit::event::DeviceId,
        event: winit::event::KeyEvent,
        is_synthetic: bool,
    ) {
        println!("----- app::handle_keyboard_input : begin handling ------");
        match event.physical_key {           
            PhysicalKey::Code(key_code) => {

                if KeyCode::Escape == key_code {
                    println!("ESCAPE KEY pressed, stopping");
                    self.close();
                    event_loop.exit();
                    return;
                }

                if KEY_SET_MOVEMENT.contains(&key_code) {
                    if let Some(move_ctrl) = &mut self.move_ctrl {
                        move_ctrl.update_key_states(key_code, event.state);
                    }
                    return;
                }

            }
            PhysicalKey::Unidentified(native_key_code) => {}
        }
    }

    pub fn handle_mouse_input(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        device_id: winit::event::DeviceId,
        state: winit::event::ElementState,
        button: winit::event::MouseButton,
    ) {
        println!("----- app::handle_mouse_input : begin handling ------");
        if state.is_pressed() && button == event::MouseButton::Left {
            print!("left mouse button clicker!");
        }
    }

    pub fn print_assets(&self) {
        if let Some(img_map) = &self.imgs {
            for (k, v) in img_map.iter() {
                println!("img : {k}");
            }
        } else {
            println!("has no imgs!");
        }
        if let Some(model_map) = &self.models {
            for (k, v) in model_map.iter() {
                println!("model : {k}");
            }
        } else {
            println!("has no models!");
        }
        if let Some(obj_map) = &self.objs {
            for (k, v) in obj_map.iter() {
                println!("obj : {k}");
            }
        } else {
            println!("has no objs!");
        }
    }
    
    pub fn close(&mut self) {
        println!("----- app-oop::close : begin closing ------");
        if let Some(p_njm_dev) = &self.dev {
            let njm_dev = p_njm_dev.lock().unwrap();
            let _ = unsafe { njm_dev.device().device_wait_idle().unwrap() };
        }
        println!("----- app-oop::close : close succeed ------");
    }
}

impl<'a> ApplicationHandler for AppOop<'a> {
    fn resumed(& mut self, event_loop: &ActiveEventLoop) {
        println!("------- ApplicationHandler::resumed : begin -------");
        // window
        let attrs: WindowAttributes = if self.wnd_attrs.is_some() {
            self.wnd_attrs.as_ref().unwrap().clone()
        } else {
            WindowAttributes::default()
        };
        self.wnd = Some(event_loop.create_window(attrs).unwrap());
        let wnd_sz = self
            .wnd_attrs
            .as_ref()
            .unwrap()
            .inner_size
            .unwrap()
            .to_physical::<u32>(1.0);
        let wnd_ex = vk::Extent2D {
            width: wnd_sz.width,
            height: wnd_sz.height,
        };
        println!("ApplicationHandler::resumed : window resumed!");
        // device, renderer(swapchain)
        if let Some(njm_dev) = &mut self.dev {
            njm_dev
                .lock()
                .unwrap()
                .recreate_surface(self.wnd.as_ref().unwrap());

            if let Some(old_render_ref) = self.renderer.as_mut() {
                old_render_ref.update_device_and_swap_chain(njm_dev.clone(), wnd_ex);
            } else {
                self.renderer = Some(NjmRenderer::new(self.dev.as_ref().unwrap().clone(), wnd_ex));
            }
        } else {
            let njm_dev = NjmDevice::new(self.wnd.as_ref().unwrap());
            self.dev = Some(Arc::new(Mutex::new(njm_dev)));
            self.renderer = Some(NjmRenderer::new(self.dev.as_ref().unwrap().clone(), wnd_ex));
        }
        println!("ApplicationHandler::resumed : device and renderer(swapchain) resumed!");
        // move_ctrl, viewer_obj, camera
        if self.move_ctrl.is_none() {
            self.move_ctrl = Some(KeyboardMovementController::default());
        }
        if self.viewer_obj.is_none() {
            self.viewer_obj = Some(NjmGameObj::create_game_obj());
            self.viewer_obj.as_mut().unwrap().transform.translation.z = DEFAULT_CAMERA_TRANSLATION_Z;
        }
        if self.camera.is_none() {
            self.camera = Some(NjmCamera::default());
        }
        println!("ApplicationHandler::resumed : camera-ctrl-sys resumed!");
        // common dev ref
        let njm_dev_ref = self.dev.as_ref().unwrap();
        
        // resources: ubo_bufs, imgs, texs, models, objs,
        if self.ubo_bufs.is_none() {
            let mut ubo_bufs: Vec<Box<NjmBuffer>> = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT as usize);
            for i in 0..MAX_FRAMES_IN_FLIGHT {
                let mut buf_ref = Box::new(NjmBuffer::new(
                    njm_dev_ref.clone(), size_of::<GlobalUbo>() as vk::DeviceSize, 1, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_VISIBLE, DEFAULT_MIN_OFFSET_ALIGN));
                buf_ref.map(DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);
                ubo_bufs.push(buf_ref);
            }
            self.ubo_bufs = Some(ubo_bufs);
        }

        if self.imgs.is_none() {
            let mut img_map = njm_img::Map::new();
            let img_name = "eg1.png";
            let img_path = IMG_BASE.to_owned() + img_name;
            let img = Arc::new(NjmImg::new(img_name, &img_path));
            img_map.insert(img_name.to_owned(), img);
            self.imgs = Some(img_map);
        }

        if self.texs.is_none() {
            let mut texs: Vec<Box<NjmTex>> = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT as usize);
            let tex_img_bindings = vec![
                TexImgBingding {tex_name: "tex1".to_string(), img_name: "eg1.png".to_string()},
            ];
            let binding = tex_img_bindings.get(0).unwrap();
            let imgs = self.imgs.as_ref().unwrap();
            for i in 0..MAX_FRAMES_IN_FLIGHT {
                let i_n = &binding.img_name;
                let t_n = &binding.tex_name;
                assert!(imgs.contains_key(i_n), "img not found : {i_n}");
                texs.push(Box::new(NjmTex::new(
                    njm_dev_ref.clone(), t_n, imgs.get(i_n).unwrap().clone()
                )));
            }           
            self.texs = Some(texs);
        }

        if self.models.is_none() {
            let mut model_map = njm_model::Map::new();
            
            let model1: Arc<NjmModel> = NjmModel::create_cube_model(njm_dev_ref.clone(), cgmath::Vector3::zero()).into();
            model_map.insert(model1.id().as_ref().unwrap().clone(), model1);

            // let model_path = MODEL_BASE.to_string() + "md1/md1.obj";
            // let model2: Arc<NjmModel> = NjmModel::create_model_from_file(njm_dev_ref.clone(), &model_path).into();
            // model_map.insert(model2.id().as_ref().unwrap().clone(), model2);
            
            self.models = Some(model_map);
        }

        if self.objs.is_none() {
            let models_ref = self.models.as_ref().unwrap();
            let mut obj_map = njm_game_obj::Map::new();
            
            let mut obj1 = NjmGameObj::create_game_obj();
            obj1.model = models_ref.get("cube").cloned();
            obj1.color = [0.1, 0.8, 0.1].into();
            obj1.transform.translation = [0.0, 0.0, 3.0].into();
            obj1.transform.scale = [1.0, 1.0, 1.0].into();
            obj1.transform.rotation = [0.5, 0.0, 0.0].into();
            obj_map.insert(*obj1.id(), obj1);

            self.objs = Some(obj_map);
        }
        println!("ApplicationHandler::resumed : resources resumed!");
        // fatal problem here!
        // global_desc_pool, global_layout_builder, global_desc_set_layout, global_desc_sets
        if self.global_desc_pool.is_none() {
            let mut builder = NjmDescriptorPoolBuilder::new(njm_dev_ref.clone());
            builder
                .set_max_sets(MAX_FRAMES_IN_FLIGHT)
                .add_pool_size(vk::DescriptorType::UNIFORM_BUFFER, MAX_FRAMES_IN_FLIGHT)
                .add_pool_size(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, MAX_FRAMES_IN_FLIGHT);
            let desc_pool = builder.build();
            self.global_desc_pool = Some(desc_pool);
        }

        if self.global_layout_builder.is_none() {
            let mut builder = NjmDescriptorSetLayoutBuilder::new(njm_dev_ref.clone());
            builder
                .add_binding(0, vk::DescriptorType::UNIFORM_BUFFER, vk::ShaderStageFlags::ALL_GRAPHICS, 1)
                .add_binding(1, vk::DescriptorType::COMBINED_IMAGE_SAMPLER, vk::ShaderStageFlags::FRAGMENT, 1);
            self.global_layout_builder = Some(builder);
        }
        let desc_set_layout = self.global_layout_builder.as_ref().unwrap().build();

        if self.global_desc_sets.is_none() {
            let ubo_bufs_ref = self.ubo_bufs.as_ref().unwrap();
            let pool_ref = self.global_desc_pool.as_ref().unwrap();
            let texs_ref = self.texs.as_ref().unwrap();

            let mut desc_sets: Vec<vk::DescriptorSet> = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT as usize);
            for i in 0..MAX_FRAMES_IN_FLIGHT as usize {
                let buf_info = ubo_bufs_ref[i].descriptor_info(DEFAULT_MAP_SIZE, DEFAULT_MAP_OFFSET);
                let img_info = texs_ref[i].get_combined_descriptor();
                let mut desc_writer = NjmDescriptorWriter::new(
                    &desc_set_layout, pool_ref
                );
                let desc_set = desc_writer
                    .write_buffer(0, &buf_info)
                    .write_image(1, &img_info)
                    .build();
                desc_sets.push(desc_set);
            }
            self.global_desc_sets = Some(desc_sets);
        }
        println!("ApplicationHandler::resumed : descriptor-sets resumed!");
        // render sys
        if self.render_sys.is_none() {
            let renderer = self.renderer.as_ref().unwrap();
            let render_pass = renderer.get_swapchain_renderpass();
            let desc_set_layout = *desc_set_layout.descriptor_set_layout();
            let vpath = SDR_BASE.to_owned() + VERT_PATH;
            let fpath = SDR_BASE.to_owned() + FRAG_PATH;
            self.render_sys = Some(SimpleRenderSystem::new(njm_dev_ref.clone(), render_pass, desc_set_layout, vpath, fpath));
        }
        println!("ApplicationHandler::resumed : render-sys resumed!");
        
        println!("ApplicationHandler::resumed : all resumed successfully");
        self.print_assets();
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        window_id: WindowId,
        event: WindowEvent,
    ) {
        match event {
            WindowEvent::CloseRequested => {
                println!("The close button was pressed; stopping");
                self.close();
                event_loop.exit();
            }
            WindowEvent::RedrawRequested => {
                println!("------- ApplicationHandler::WindowEvent::RedrawRequested -------");
                self.redraw(event_loop, window_id); // rendering                 
            }
            WindowEvent::Resized(size) => {
                println!(
                    "window_event::resized: width: {} , height: {}",
                    size.width, size.height
                );
                if size.width == 0 || size.height == 0 {
                    return;
                }
                assert!(
                    self.dev.is_some()
                        && self.wnd.is_some()
                        && self.wnd_attrs.is_some()
                        && self.renderer.is_some()
                );

                let m_wnd_attrs = self.wnd_attrs.as_mut().unwrap();
                m_wnd_attrs.inner_size = Some(winit::dpi::Size::Physical(size));

                let wnd_ex: vk::Extent2D = get_extent_from_physical_size(size);
                self.renderer.as_mut().unwrap().update_swap_chain(wnd_ex);
            }
            WindowEvent::KeyboardInput {
                device_id,
                event,
                is_synthetic,
            } => {
                self.handle_keyboard_input(event_loop, window_id, device_id, event, is_synthetic);
            }
            WindowEvent::MouseInput {
                device_id,
                state,
                button,
            } => {
                self.handle_mouse_input(event_loop, window_id, device_id, state, button);
            }
            _ => (),
        }
    }

    fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
        let _ = (event_loop, cause);
    }

    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: ()) {
        let _ = (event_loop, event);
    }

    fn device_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        device_id: winit::event::DeviceId,
        event: winit::event::DeviceEvent,
    ) {
        let _ = (event_loop, device_id, event);
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        let _ = event_loop;
    }

    fn suspended(&mut self, event_loop: &ActiveEventLoop) {
        let _ = event_loop;
    }

    fn exiting(&mut self, event_loop: &ActiveEventLoop) {
        let _ = event_loop;
    }

    fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
        let _ = event_loop;
    }
}
